/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.buffer;

import static io.netty.util.internal.MathUtil.isOutOfBounds;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;

import io.netty.util.AsciiString;
import io.netty.util.ByteProcessor;
import io.netty.util.CharsetUtil;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.charset.Charset;

/**
 * A skeletal implementation of a buffer.
 */
public abstract class AbstractByteBuf extends ByteBuf {

  private static final InternalLogger logger = InternalLoggerFactory
      .getInstance(AbstractByteBuf.class);
  private static final String LEGACY_PROP_CHECK_ACCESSIBLE = "io.netty.buffer.bytebuf.checkAccessible";
  private static final String PROP_CHECK_ACCESSIBLE = "io.netty.buffer.checkAccessible";
  static final boolean checkAccessible; // accessed from CompositeByteBuf
  private static final String PROP_CHECK_BOUNDS = "io.netty.buffer.checkBounds";
  private static final boolean checkBounds;

  static {
    if (SystemPropertyUtil.contains(PROP_CHECK_ACCESSIBLE)) {
      checkAccessible = SystemPropertyUtil.getBoolean(PROP_CHECK_ACCESSIBLE, true);
    } else {
      checkAccessible = SystemPropertyUtil.getBoolean(LEGACY_PROP_CHECK_ACCESSIBLE, true);
    }
    checkBounds = SystemPropertyUtil.getBoolean(PROP_CHECK_BOUNDS, true);
    if (logger.isDebugEnabled()) {
      logger.debug("-D{}: {}", PROP_CHECK_ACCESSIBLE, checkAccessible);
      logger.debug("-D{}: {}", PROP_CHECK_BOUNDS, checkBounds);
    }
  }

  static final ResourceLeakDetector<ByteBuf> leakDetector =
      ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class);

  int readerIndex;
  int writerIndex;
  private int markedReaderIndex;
  private int markedWriterIndex;
  private int maxCapacity;

  protected AbstractByteBuf(int maxCapacity) {
    checkPositiveOrZero(maxCapacity, "maxCapacity");
    this.maxCapacity = maxCapacity;
  }

  @Override
  public boolean isReadOnly() {
    return false;
  }

  @SuppressWarnings("deprecation")
  @Override
  public ByteBuf asReadOnly() {
    if (isReadOnly()) {
      return this;
    }
    return Unpooled.unmodifiableBuffer(this);
  }

  @Override
  public int maxCapacity() {
    return maxCapacity;
  }

  protected final void maxCapacity(int maxCapacity) {
    this.maxCapacity = maxCapacity;
  }

  @Override
  public int readerIndex() {
    return readerIndex;
  }

  private static void checkIndexBounds(final int readerIndex, final int writerIndex,
      final int capacity) {
    if (readerIndex < 0 || readerIndex > writerIndex || writerIndex > capacity) {
      throw new IndexOutOfBoundsException(String.format(
          "readerIndex: %d, writerIndex: %d (expected: 0 <= readerIndex <= writerIndex <= capacity(%d))",
          readerIndex, writerIndex, capacity));
    }
  }

  @Override
  public ByteBuf readerIndex(int readerIndex) {
    if (checkBounds) {
      checkIndexBounds(readerIndex, writerIndex, capacity());
    }
    this.readerIndex = readerIndex;
    return this;
  }

  @Override
  public int writerIndex() {
    return writerIndex;
  }

  @Override
  public ByteBuf writerIndex(int writerIndex) {
    if (checkBounds) {
      checkIndexBounds(readerIndex, writerIndex, capacity());
    }
    this.writerIndex = writerIndex;
    return this;
  }

  @Override
  public ByteBuf setIndex(int readerIndex, int writerIndex) {
    if (checkBounds) {
      checkIndexBounds(readerIndex, writerIndex, capacity());
    }
    setIndex0(readerIndex, writerIndex);
    return this;
  }

  /**
   * clear()方法直接将readerIndex和writerIndex重置为0，而无论其是否做过标记
   */
  @Override
  public ByteBuf clear() {
    readerIndex = writerIndex = 0;
    return this;
  }

  @Override
  public boolean isReadable() {
    return writerIndex > readerIndex;
  }

  @Override
  public boolean isReadable(int numBytes) {
    return writerIndex - readerIndex >= numBytes;
  }

  @Override
  public boolean isWritable() {
    return capacity() > writerIndex;
  }

  @Override
  public boolean isWritable(int numBytes) {
    return capacity() - writerIndex >= numBytes;
  }

  @Override
  public int readableBytes() {
    return writerIndex - readerIndex;
  }

  @Override
  public int writableBytes() {
    return capacity() - writerIndex;
  }

  @Override
  public int maxWritableBytes() {
    return maxCapacity() - writerIndex;
  }

  @Override
  public ByteBuf markReaderIndex() {
    markedReaderIndex = readerIndex;
    return this;
  }

  @Override
  public ByteBuf resetReaderIndex() {
    readerIndex(markedReaderIndex);
    return this;
  }

  @Override
  public ByteBuf markWriterIndex() {
    markedWriterIndex = writerIndex;
    return this;
  }

  @Override
  public ByteBuf resetWriterIndex() {
    writerIndex(markedWriterIndex);
    return this;
  }

  @Override
  public ByteBuf discardReadBytes() {
    ensureAccessible();
    if (readerIndex == 0) {
      return this;
    }

    if (readerIndex != writerIndex) {
      setBytes(0, this, readerIndex, writerIndex - readerIndex);
      writerIndex -= readerIndex;
      adjustMarkers(readerIndex);
      readerIndex = 0;
    } else {
      adjustMarkers(readerIndex);
      writerIndex = readerIndex = 0;
    }
    return this;
  }

  /**
   * 该方法会根据如下情况对readerIndex和writerIndex进行调整： ①readerIndex与writerIndex相同，则将其进行重置；
   * ②如果readerIndex超过了总容量的一半，则对其进行重置；
   */
  @Override
  public ByteBuf discardSomeReadBytes() {
    ensureAccessible();
    if (readerIndex == 0) {
      return this;
    }

    if (readerIndex == writerIndex) {
      adjustMarkers(readerIndex);
      writerIndex = readerIndex = 0;
      return this;
    }

    if (readerIndex >= capacity() >>> 1) {
      setBytes(0, this, readerIndex, writerIndex - readerIndex);
      writerIndex -= readerIndex;
      adjustMarkers(readerIndex);
      readerIndex = 0;
    }
    return this;
  }

  protected final void adjustMarkers(int decrement) {
    int markedReaderIndex = this.markedReaderIndex;
    if (markedReaderIndex <= decrement) {
      this.markedReaderIndex = 0;
      int markedWriterIndex = this.markedWriterIndex;
      if (markedWriterIndex <= decrement) {
        this.markedWriterIndex = 0;
      } else {
        this.markedWriterIndex = markedWriterIndex - decrement;
      }
    } else {
      this.markedReaderIndex = markedReaderIndex - decrement;
      markedWriterIndex -= decrement;
    }
  }

  @Override
  public ByteBuf ensureWritable(int minWritableBytes) {
    checkPositiveOrZero(minWritableBytes, "minWritableBytes");
    ensureWritable0(minWritableBytes);
    return this;
  }

  final void ensureWritable0(int minWritableBytes) {
    ensureAccessible();
    if (minWritableBytes <= writableBytes()) {
      return;
    }
    if (checkBounds) {
      if (minWritableBytes > maxCapacity - writerIndex) {
        throw new IndexOutOfBoundsException(String.format(
            "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
            writerIndex, minWritableBytes, maxCapacity, this));
      }
    }

    // Normalize the current capacity to the power of 2.
    int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);

    // Adjust to the new capacity.
    capacity(newCapacity);
  }

  @Override
  public int ensureWritable(int minWritableBytes, boolean force) {
    ensureAccessible();
    checkPositiveOrZero(minWritableBytes, "minWritableBytes");

    if (minWritableBytes <= writableBytes()) {
      return 0;
    }

    final int maxCapacity = maxCapacity();
    final int writerIndex = writerIndex();
    if (minWritableBytes > maxCapacity - writerIndex) {
      if (!force || capacity() == maxCapacity) {
        return 1;
      }

      capacity(maxCapacity);
      return 3;
    }

    // Normalize the current capacity to the power of 2.
    int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);

    // Adjust to the new capacity.
    capacity(newCapacity);
    return 2;
  }

  @Override
  public ByteBuf order(ByteOrder endianness) {
    if (endianness == order()) {
      return this;
    }
    if (endianness == null) {
      throw new NullPointerException("endianness");
    }
    return newSwappedByteBuf();
  }

  /**
   * Creates a new {@link SwappedByteBuf} for this {@link ByteBuf} instance.
   */
  protected SwappedByteBuf newSwappedByteBuf() {
    return new SwappedByteBuf(this);
  }

  @Override
  public byte getByte(int index) {
    checkIndex(index);
    return _getByte(index);
  }

  protected abstract byte _getByte(int index);

  @Override
  public boolean getBoolean(int index) {
    return getByte(index) != 0;
  }

  @Override
  public short getUnsignedByte(int index) {
    return (short) (getByte(index) & 0xFF);
  }

  @Override
  public short getShort(int index) {
    checkIndex(index, 2);
    return _getShort(index);
  }

  protected abstract short _getShort(int index);

  @Override
  public short getShortLE(int index) {
    checkIndex(index, 2);
    return _getShortLE(index);
  }

  protected abstract short _getShortLE(int index);

  @Override
  public int getUnsignedShort(int index) {
    return getShort(index) & 0xFFFF;
  }

  @Override
  public int getUnsignedShortLE(int index) {
    return getShortLE(index) & 0xFFFF;
  }

  @Override
  public int getUnsignedMedium(int index) {
    checkIndex(index, 3);
    return _getUnsignedMedium(index);
  }

  protected abstract int _getUnsignedMedium(int index);

  @Override
  public int getUnsignedMediumLE(int index) {
    checkIndex(index, 3);
    return _getUnsignedMediumLE(index);
  }

  protected abstract int _getUnsignedMediumLE(int index);

  @Override
  public int getMedium(int index) {
    int value = getUnsignedMedium(index);
    if ((value & 0x800000) != 0) {
      value |= 0xff000000;
    }
    return value;
  }

  @Override
  public int getMediumLE(int index) {
    int value = getUnsignedMediumLE(index);
    if ((value & 0x800000) != 0) {
      value |= 0xff000000;
    }
    return value;
  }

  @Override
  public int getInt(int index) {
    checkIndex(index, 4);
    return _getInt(index);
  }

  protected abstract int _getInt(int index);

  @Override
  public int getIntLE(int index) {
    checkIndex(index, 4);
    return _getIntLE(index);
  }

  protected abstract int _getIntLE(int index);

  @Override
  public long getUnsignedInt(int index) {
    return getInt(index) & 0xFFFFFFFFL;
  }

  @Override
  public long getUnsignedIntLE(int index) {
    return getIntLE(index) & 0xFFFFFFFFL;
  }

  @Override
  public long getLong(int index) {
    checkIndex(index, 8);
    return _getLong(index);
  }

  protected abstract long _getLong(int index);

  @Override
  public long getLongLE(int index) {
    checkIndex(index, 8);
    return _getLongLE(index);
  }

  protected abstract long _getLongLE(int index);

  @Override
  public char getChar(int index) {
    return (char) getShort(index);
  }

  @Override
  public float getFloat(int index) {
    return Float.intBitsToFloat(getInt(index));
  }

  @Override
  public double getDouble(int index) {
    return Double.longBitsToDouble(getLong(index));
  }

  @Override
  public ByteBuf getBytes(int index, byte[] dst) {
    getBytes(index, dst, 0, dst.length);
    return this;
  }

  @Override
  public ByteBuf getBytes(int index, ByteBuf dst) {
    getBytes(index, dst, dst.writableBytes());
    return this;
  }

  @Override
  public ByteBuf getBytes(int index, ByteBuf dst, int length) {
    getBytes(index, dst, dst.writerIndex(), length);
    dst.writerIndex(dst.writerIndex() + length);
    return this;
  }

  @Override
  public CharSequence getCharSequence(int index, int length, Charset charset) {
    if (CharsetUtil.US_ASCII.equals(charset) || CharsetUtil.ISO_8859_1.equals(charset)) {
      // ByteBufUtil.getBytes(...) will return a new copy which the AsciiString uses directly
      return new AsciiString(ByteBufUtil.getBytes(this, index, length, true), false);
    }
    return toString(index, length, charset);
  }

  @Override
  public CharSequence readCharSequence(int length, Charset charset) {
    CharSequence sequence = getCharSequence(readerIndex, length, charset);
    readerIndex += length;
    return sequence;
  }

  @Override
  public ByteBuf setByte(int index, int value) {
    checkIndex(index);
    _setByte(index, value);
    return this;
  }

  protected abstract void _setByte(int index, int value);

  @Override
  public ByteBuf setBoolean(int index, boolean value) {
    setByte(index, value ? 1 : 0);
    return this;
  }

  @Override
  public ByteBuf setShort(int index, int value) {
    checkIndex(index, 2);
    _setShort(index, value);
    return this;
  }

  protected abstract void _setShort(int index, int value);

  @Override
  public ByteBuf setShortLE(int index, int value) {
    checkIndex(index, 2);
    _setShortLE(index, value);
    return this;
  }

  protected abstract void _setShortLE(int index, int value);

  @Override
  public ByteBuf setChar(int index, int value) {
    setShort(index, value);
    return this;
  }

  @Override
  public ByteBuf setMedium(int index, int value) {
    checkIndex(index, 3);
    _setMedium(index, value);
    return this;
  }

  protected abstract void _setMedium(int index, int value);

  @Override
  public ByteBuf setMediumLE(int index, int value) {
    checkIndex(index, 3);
    _setMediumLE(index, value);
    return this;
  }

  protected abstract void _setMediumLE(int index, int value);

  @Override
  public ByteBuf setInt(int index, int value) {
    checkIndex(index, 4);
    _setInt(index, value);
    return this;
  }

  protected abstract void _setInt(int index, int value);

  @Override
  public ByteBuf setIntLE(int index, int value) {
    checkIndex(index, 4);
    _setIntLE(index, value);
    return this;
  }

  protected abstract void _setIntLE(int index, int value);

  @Override
  public ByteBuf setFloat(int index, float value) {
    setInt(index, Float.floatToRawIntBits(value));
    return this;
  }

  @Override
  public ByteBuf setLong(int index, long value) {
    checkIndex(index, 8);
    _setLong(index, value);
    return this;
  }

  protected abstract void _setLong(int index, long value);

  @Override
  public ByteBuf setLongLE(int index, long value) {
    checkIndex(index, 8);
    _setLongLE(index, value);
    return this;
  }

  protected abstract void _setLongLE(int index, long value);

  @Override
  public ByteBuf setDouble(int index, double value) {
    setLong(index, Double.doubleToRawLongBits(value));
    return this;
  }

  @Override
  public ByteBuf setBytes(int index, byte[] src) {
    setBytes(index, src, 0, src.length);
    return this;
  }

  @Override
  public ByteBuf setBytes(int index, ByteBuf src) {
    setBytes(index, src, src.readableBytes());
    return this;
  }

  private static void checkReadableBounds(final ByteBuf src, final int length) {
    if (length > src.readableBytes()) {
      throw new IndexOutOfBoundsException(String.format(
          "length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(),
          src));
    }
  }

  @Override
  public ByteBuf setBytes(int index, ByteBuf src, int length) {
    checkIndex(index, length);
    if (src == null) {
      throw new NullPointerException("src");
    }
    if (checkBounds) {
      checkReadableBounds(src, length);
    }

    setBytes(index, src, src.readerIndex(), length);
    src.readerIndex(src.readerIndex() + length);
    return this;
  }

  @Override
  public ByteBuf setZero(int index, int length) {
    if (length == 0) {
      return this;
    }

    checkIndex(index, length);

    int nLong = length >>> 3;
    int nBytes = length & 7;
    for (int i = nLong; i > 0; i--) {
      _setLong(index, 0);
      index += 8;
    }
    if (nBytes == 4) {
      _setInt(index, 0);
      // Not need to update the index as we not will use it after this.
    } else if (nBytes < 4) {
      for (int i = nBytes; i > 0; i--) {
        _setByte(index, (byte) 0);
        index++;
      }
    } else {
      _setInt(index, 0);
      index += 4;
      for (int i = nBytes - 4; i > 0; i--) {
        _setByte(index, (byte) 0);
        index++;
      }
    }
    return this;
  }

  @Override
  public int setCharSequence(int index, CharSequence sequence, Charset charset) {
    return setCharSequence0(index, sequence, charset, false);
  }

  private int setCharSequence0(int index, CharSequence sequence, Charset charset, boolean expand) {
    if (charset.equals(CharsetUtil.UTF_8)) {
      int length = ByteBufUtil.utf8MaxBytes(sequence);
      if (expand) {
        ensureWritable0(length);
        checkIndex0(index, length);
      } else {
        checkIndex(index, length);
      }
      return ByteBufUtil.writeUtf8(this, index, sequence, sequence.length());
    }
    if (charset.equals(CharsetUtil.US_ASCII) || charset.equals(CharsetUtil.ISO_8859_1)) {
      int length = sequence.length();
      if (expand) {
        ensureWritable0(length);
        checkIndex0(index, length);
      } else {
        checkIndex(index, length);
      }
      return ByteBufUtil.writeAscii(this, index, sequence, length);
    }
    byte[] bytes = sequence.toString().getBytes(charset);
    if (expand) {
      ensureWritable0(bytes.length);
      // setBytes(...) will take care of checking the indices.
    }
    setBytes(index, bytes);
    return bytes.length;
  }

  @Override
  public byte readByte() {
    checkReadableBytes0(1);
    int i = readerIndex;
    byte b = _getByte(i);
    readerIndex = i + 1;
    return b;
  }

  @Override
  public boolean readBoolean() {
    return readByte() != 0;
  }

  @Override
  public short readUnsignedByte() {
    return (short) (readByte() & 0xFF);
  }

  @Override
  public short readShort() {
    checkReadableBytes0(2);
    short v = _getShort(readerIndex);
    readerIndex += 2;
    return v;
  }

  @Override
  public short readShortLE() {
    checkReadableBytes0(2);
    short v = _getShortLE(readerIndex);
    readerIndex += 2;
    return v;
  }

  @Override
  public int readUnsignedShort() {
    return readShort() & 0xFFFF;
  }

  @Override
  public int readUnsignedShortLE() {
    return readShortLE() & 0xFFFF;
  }

  @Override
  public int readMedium() {
    int value = readUnsignedMedium();
    if ((value & 0x800000) != 0) {
      value |= 0xff000000;
    }
    return value;
  }

  @Override
  public int readMediumLE() {
    int value = readUnsignedMediumLE();
    if ((value & 0x800000) != 0) {
      value |= 0xff000000;
    }
    return value;
  }

  @Override
  public int readUnsignedMedium() {
    checkReadableBytes0(3);
    int v = _getUnsignedMedium(readerIndex);
    readerIndex += 3;
    return v;
  }

  @Override
  public int readUnsignedMediumLE() {
    checkReadableBytes0(3);
    int v = _getUnsignedMediumLE(readerIndex);
    readerIndex += 3;
    return v;
  }

  @Override
  public int readInt() {
    checkReadableBytes0(4);
    int v = _getInt(readerIndex);
    readerIndex += 4;
    return v;
  }

  @Override
  public int readIntLE() {
    checkReadableBytes0(4);
    int v = _getIntLE(readerIndex);
    readerIndex += 4;
    return v;
  }

  @Override
  public long readUnsignedInt() {
    return readInt() & 0xFFFFFFFFL;
  }

  @Override
  public long readUnsignedIntLE() {
    return readIntLE() & 0xFFFFFFFFL;
  }

  @Override
  public long readLong() {
    checkReadableBytes0(8);
    long v = _getLong(readerIndex);
    readerIndex += 8;
    return v;
  }

  @Override
  public long readLongLE() {
    checkReadableBytes0(8);
    long v = _getLongLE(readerIndex);
    readerIndex += 8;
    return v;
  }

  @Override
  public char readChar() {
    return (char) readShort();
  }

  @Override
  public float readFloat() {
    return Float.intBitsToFloat(readInt());
  }

  @Override
  public double readDouble() {
    return Double.longBitsToDouble(readLong());
  }

  @Override
  public ByteBuf readBytes(int length) {
    checkReadableBytes(length);
    if (length == 0) {
      return Unpooled.EMPTY_BUFFER;
    }

    ByteBuf buf = alloc().buffer(length, maxCapacity);
    buf.writeBytes(this, readerIndex, length);
    readerIndex += length;
    return buf;
  }

  @Override
  public ByteBuf readSlice(int length) {
    checkReadableBytes(length);
    ByteBuf slice = slice(readerIndex, length);
    readerIndex += length;
    return slice;
  }

  @Override
  public ByteBuf readRetainedSlice(int length) {
    checkReadableBytes(length);
    ByteBuf slice = retainedSlice(readerIndex, length);
    readerIndex += length;
    return slice;
  }

  @Override
  public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
    checkReadableBytes(length);
    getBytes(readerIndex, dst, dstIndex, length);
    readerIndex += length;
    return this;
  }

  @Override
  public ByteBuf readBytes(byte[] dst) {
    readBytes(dst, 0, dst.length);
    return this;
  }

  @Override
  public ByteBuf readBytes(ByteBuf dst) {
    readBytes(dst, dst.writableBytes());
    return this;
  }

  @Override
  public ByteBuf readBytes(ByteBuf dst, int length) {
    if (checkBounds) {
      if (length > dst.writableBytes()) {
        throw new IndexOutOfBoundsException(String.format(
            "length(%d) exceeds dst.writableBytes(%d) where dst is: %s", length,
            dst.writableBytes(), dst));
      }
    }
    readBytes(dst, dst.writerIndex(), length);
    dst.writerIndex(dst.writerIndex() + length);
    return this;
  }

  @Override
  public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
    checkReadableBytes(length);
    getBytes(readerIndex, dst, dstIndex, length);
    readerIndex += length;
    return this;
  }

  @Override
  public ByteBuf readBytes(ByteBuffer dst) {
    int length = dst.remaining();
    checkReadableBytes(length);
    getBytes(readerIndex, dst);
    readerIndex += length;
    return this;
  }

  @Override
  public int readBytes(GatheringByteChannel out, int length)
      throws IOException {
    checkReadableBytes(length);
    int readBytes = getBytes(readerIndex, out, length);
    readerIndex += readBytes;
    return readBytes;
  }

  @Override
  public int readBytes(FileChannel out, long position, int length)
      throws IOException {
    checkReadableBytes(length);
    int readBytes = getBytes(readerIndex, out, position, length);
    readerIndex += readBytes;
    return readBytes;
  }

  @Override
  public ByteBuf readBytes(OutputStream out, int length) throws IOException {
    checkReadableBytes(length);
    getBytes(readerIndex, out, length);
    readerIndex += length;
    return this;
  }

  @Override
  public ByteBuf skipBytes(int length) {
    checkReadableBytes(length);
    readerIndex += length;
    return this;
  }

  @Override
  public ByteBuf writeBoolean(boolean value) {
    writeByte(value ? 1 : 0);
    return this;
  }

  @Override
  public ByteBuf writeByte(int value) {
    ensureWritable0(1);
    _setByte(writerIndex++, value);
    return this;
  }

  @Override
  public ByteBuf writeShort(int value) {
    ensureWritable0(2);
    _setShort(writerIndex, value);
    writerIndex += 2;
    return this;
  }

  @Override
  public ByteBuf writeShortLE(int value) {
    ensureWritable0(2);
    _setShortLE(writerIndex, value);
    writerIndex += 2;
    return this;
  }

  @Override
  public ByteBuf writeMedium(int value) {
    ensureWritable0(3);
    _setMedium(writerIndex, value);
    writerIndex += 3;
    return this;
  }

  @Override
  public ByteBuf writeMediumLE(int value) {
    ensureWritable0(3);
    _setMediumLE(writerIndex, value);
    writerIndex += 3;
    return this;
  }

  @Override
  public ByteBuf writeInt(int value) {
    ensureWritable0(4);
    _setInt(writerIndex, value);
    writerIndex += 4;
    return this;
  }

  @Override
  public ByteBuf writeIntLE(int value) {
    ensureWritable0(4);
    _setIntLE(writerIndex, value);
    writerIndex += 4;
    return this;
  }

  @Override
  public ByteBuf writeLong(long value) {
    ensureWritable0(8);
    _setLong(writerIndex, value);
    writerIndex += 8;
    return this;
  }

  @Override
  public ByteBuf writeLongLE(long value) {
    ensureWritable0(8);
    _setLongLE(writerIndex, value);
    writerIndex += 8;
    return this;
  }

  @Override
  public ByteBuf writeChar(int value) {
    writeShort(value);
    return this;
  }

  @Override
  public ByteBuf writeFloat(float value) {
    writeInt(Float.floatToRawIntBits(value));
    return this;
  }

  @Override
  public ByteBuf writeDouble(double value) {
    writeLong(Double.doubleToRawLongBits(value));
    return this;
  }

  @Override
  public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
    ensureWritable(length);
    setBytes(writerIndex, src, srcIndex, length);
    writerIndex += length;
    return this;
  }

  @Override
  public ByteBuf writeBytes(byte[] src) {
    writeBytes(src, 0, src.length);
    return this;
  }

  @Override
  public ByteBuf writeBytes(ByteBuf src) {
    writeBytes(src, src.readableBytes());
    return this;
  }

  @Override
  public ByteBuf writeBytes(ByteBuf src, int length) {
    if (checkBounds) {
      checkReadableBounds(src, length);
    }
    writeBytes(src, src.readerIndex(), length);
    src.readerIndex(src.readerIndex() + length);
    return this;
  }

  @Override
  public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
    ensureWritable(length);
    setBytes(writerIndex, src, srcIndex, length);
    writerIndex += length;
    return this;
  }

  @Override
  public ByteBuf writeBytes(ByteBuffer src) {
    int length = src.remaining();
    ensureWritable0(length);
    setBytes(writerIndex, src);
    writerIndex += length;
    return this;
  }

  @Override
  public int writeBytes(InputStream in, int length)
      throws IOException {
    ensureWritable(length);
    int writtenBytes = setBytes(writerIndex, in, length);
    if (writtenBytes > 0) {
      writerIndex += writtenBytes;
    }
    return writtenBytes;
  }

  @Override
  public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
    ensureWritable(length);
    int writtenBytes = setBytes(writerIndex, in, length);
    if (writtenBytes > 0) {
      writerIndex += writtenBytes;
    }
    return writtenBytes;
  }

  @Override
  public int writeBytes(FileChannel in, long position, int length) throws IOException {
    ensureWritable(length);
    int writtenBytes = setBytes(writerIndex, in, position, length);
    if (writtenBytes > 0) {
      writerIndex += writtenBytes;
    }
    return writtenBytes;
  }

  @Override
  public ByteBuf writeZero(int length) {
    if (length == 0) {
      return this;
    }

    ensureWritable(length);
    int wIndex = writerIndex;
    checkIndex0(wIndex, length);

    int nLong = length >>> 3;
    int nBytes = length & 7;
    for (int i = nLong; i > 0; i--) {
      _setLong(wIndex, 0);
      wIndex += 8;
    }
    if (nBytes == 4) {
      _setInt(wIndex, 0);
      wIndex += 4;
    } else if (nBytes < 4) {
      for (int i = nBytes; i > 0; i--) {
        _setByte(wIndex, (byte) 0);
        wIndex++;
      }
    } else {
      _setInt(wIndex, 0);
      wIndex += 4;
      for (int i = nBytes - 4; i > 0; i--) {
        _setByte(wIndex, (byte) 0);
        wIndex++;
      }
    }
    writerIndex = wIndex;
    return this;
  }

  @Override
  public int writeCharSequence(CharSequence sequence, Charset charset) {
    int written = setCharSequence0(writerIndex, sequence, charset, true);
    writerIndex += written;
    return written;
  }

  @Override
  public ByteBuf copy() {
    return copy(readerIndex, readableBytes());
  }

  @Override
  public ByteBuf duplicate() {
    ensureAccessible();
    return new UnpooledDuplicatedByteBuf(this);
  }

  @Override
  public ByteBuf retainedDuplicate() {
    return duplicate().retain();
  }

  @Override
  public ByteBuf slice() {
    return slice(readerIndex, readableBytes());
  }

  @Override
  public ByteBuf retainedSlice() {
    return slice().retain();
  }

  @Override
  public ByteBuf slice(int index, int length) {
    ensureAccessible();
    return new UnpooledSlicedByteBuf(this, index, length);
  }

  @Override
  public ByteBuf retainedSlice(int index, int length) {
    return slice(index, length).retain();
  }

  @Override
  public ByteBuffer nioBuffer() {
    return nioBuffer(readerIndex, readableBytes());
  }

  @Override
  public ByteBuffer[] nioBuffers() {
    return nioBuffers(readerIndex, readableBytes());
  }

  @Override
  public String toString(Charset charset) {
    return toString(readerIndex, readableBytes(), charset);
  }

  @Override
  public String toString(int index, int length, Charset charset) {
    return ByteBufUtil.decodeString(this, index, length, charset);
  }

  @Override
  public int indexOf(int fromIndex, int toIndex, byte value) {
    return ByteBufUtil.indexOf(this, fromIndex, toIndex, value);
  }

  @Override
  public int bytesBefore(byte value) {
    return bytesBefore(readerIndex(), readableBytes(), value);
  }

  @Override
  public int bytesBefore(int length, byte value) {
    checkReadableBytes(length);
    return bytesBefore(readerIndex(), length, value);
  }

  @Override
  public int bytesBefore(int index, int length, byte value) {
    int endIndex = indexOf(index, index + length, value);
    if (endIndex < 0) {
      return -1;
    }
    return endIndex - index;
  }

  @Override
  public int forEachByte(ByteProcessor processor) {
    ensureAccessible();
    try {
      return forEachByteAsc0(readerIndex, writerIndex, processor);
    } catch (Exception e) {
      PlatformDependent.throwException(e);
      return -1;
    }
  }

  @Override
  public int forEachByte(int index, int length, ByteProcessor processor) {
    checkIndex(index, length);
    try {
      return forEachByteAsc0(index, index + length, processor);
    } catch (Exception e) {
      PlatformDependent.throwException(e);
      return -1;
    }
  }

  int forEachByteAsc0(int start, int end, ByteProcessor processor) throws Exception {
    for (; start < end; ++start) {
      if (!processor.process(_getByte(start))) {
        return start;
      }
    }

    return -1;
  }

  @Override
  public int forEachByteDesc(ByteProcessor processor) {
    ensureAccessible();
    try {
      return forEachByteDesc0(writerIndex - 1, readerIndex, processor);
    } catch (Exception e) {
      PlatformDependent.throwException(e);
      return -1;
    }
  }

  @Override
  public int forEachByteDesc(int index, int length, ByteProcessor processor) {
    checkIndex(index, length);
    try {
      return forEachByteDesc0(index + length - 1, index, processor);
    } catch (Exception e) {
      PlatformDependent.throwException(e);
      return -1;
    }
  }

  int forEachByteDesc0(int rStart, final int rEnd, ByteProcessor processor) throws Exception {
    for (; rStart >= rEnd; --rStart) {
      if (!processor.process(_getByte(rStart))) {
        return rStart;
      }
    }
    return -1;
  }

  @Override
  public int hashCode() {
    return ByteBufUtil.hashCode(this);
  }

  @Override
  public boolean equals(Object o) {
    return this == o || (o instanceof ByteBuf && ByteBufUtil.equals(this, (ByteBuf) o));
  }

  @Override
  public int compareTo(ByteBuf that) {
    return ByteBufUtil.compare(this, that);
  }

  @Override
  public String toString() {
    if (refCnt() == 0) {
      return StringUtil.simpleClassName(this) + "(freed)";
    }

    StringBuilder buf = new StringBuilder()
        .append(StringUtil.simpleClassName(this))
        .append("(ridx: ").append(readerIndex)
        .append(", widx: ").append(writerIndex)
        .append(", cap: ").append(capacity());
    if (maxCapacity != Integer.MAX_VALUE) {
      buf.append('/').append(maxCapacity);
    }

    ByteBuf unwrapped = unwrap();
    if (unwrapped != null) {
      buf.append(", unwrapped: ").append(unwrapped);
    }
    buf.append(')');
    return buf.toString();
  }

  protected final void checkIndex(int index) {
    checkIndex(index, 1);
  }

  protected final void checkIndex(int index, int fieldLength) {
    ensureAccessible();
    checkIndex0(index, fieldLength);
  }

  private static void checkRangeBounds(final int index, final int fieldLength, final int capacity) {
    if (isOutOfBounds(index, fieldLength, capacity)) {
      throw new IndexOutOfBoundsException(String.format(
          "index: %d, length: %d (expected: range(0, %d))", index, fieldLength, capacity));
    }
  }

  final void checkIndex0(int index, int fieldLength) {
    if (checkBounds) {
      checkRangeBounds(index, fieldLength, capacity());
    }
  }

  protected final void checkSrcIndex(int index, int length, int srcIndex, int srcCapacity) {
    checkIndex(index, length);
    if (checkBounds) {
      checkRangeBounds(srcIndex, length, srcCapacity);
    }
  }

  protected final void checkDstIndex(int index, int length, int dstIndex, int dstCapacity) {
    checkIndex(index, length);
    if (checkBounds) {
      checkRangeBounds(dstIndex, length, dstCapacity);
    }
  }

  /**
   * Throws an {@link IndexOutOfBoundsException} if the current {@linkplain #readableBytes()
   * readable bytes} of this buffer is less than the specified value.
   */
  protected final void checkReadableBytes(int minimumReadableBytes) {
    checkPositiveOrZero(minimumReadableBytes, "minimumReadableBytes");
    checkReadableBytes0(minimumReadableBytes);
  }

  protected final void checkNewCapacity(int newCapacity) {
    ensureAccessible();
    if (checkBounds) {
      if (newCapacity < 0 || newCapacity > maxCapacity()) {
        throw new IllegalArgumentException("newCapacity: " + newCapacity +
            " (expected: 0-" + maxCapacity() + ')');
      }
    }
  }

  private void checkReadableBytes0(int minimumReadableBytes) {
    ensureAccessible();
    if (checkBounds) {
      if (readerIndex > writerIndex - minimumReadableBytes) {
        throw new IndexOutOfBoundsException(String.format(
            "readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
            readerIndex, minimumReadableBytes, writerIndex, this));
      }
    }
  }

  /**
   * Should be called by every method that tries to access the buffers content to check if the
   * buffer was released before.
   */
  protected final void ensureAccessible() {
    if (checkAccessible && internalRefCnt() == 0) {
      throw new IllegalReferenceCountException(0);
    }
  }

  /**
   * Returns the reference count that is used internally by {@link #ensureAccessible()} to try to
   * guard against using the buffer after it was released (best-effort).
   */
  int internalRefCnt() {
    return refCnt();
  }

  final void setIndex0(int readerIndex, int writerIndex) {
    this.readerIndex = readerIndex;
    this.writerIndex = writerIndex;
  }

  final void discardMarks() {
    markedReaderIndex = markedWriterIndex = 0;
  }
}
